package com.gitee.free.coding.core.exceptions.handler;

import com.gitee.free.coding.core.enums.ErrorEnum;
import com.gitee.free.coding.core.exceptions.FreeRuntimeException;
import com.gitee.free.coding.core.utils.EmptyData;
import com.gitee.free.coding.core.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 全局异常统一处理类
 * 注意：需要保证 Spring 扫描到当前包或类
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = FreeRuntimeException.class)
    public Result<EmptyData> handleFreeRuntimeException(FreeRuntimeException e) {
        log.error(e.getMessage(), e);
        return Result.setResult(e.getErrorCode(), e.getErrorMsg());
    }

    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result<List<String>> handleConstraintViolationException(ConstraintViolationException e) {
        List<String> errors;
        try {
            errors = getErrors(e);
        } catch (Exception ex) {
            log.error(ex.getMessage(), e);
            return Result.setResult(ErrorEnum.ARGS_CHECK_FAIL);
        }
        // e.getMessage()的值如右  ==>  login.arg0.userName: 不是邮箱  ==>  【表示 login 方法中的 第0个参数的 userName 属性值不合法】
        log.error(e.getMessage(), e);
        return Result.setResult(ErrorEnum.ARGS_CHECK_FAIL, errors);
    }

    /**
     * 获取错误信息列表
     *
     * @param e 异常
     * @return 错误信息列表
     * @throws NoSuchFieldException NoSuchFieldException
     */
    private List<String> getErrors(ConstraintViolationException e) throws NoSuchFieldException {
        // 记录错误信息
        List<String> errors = new ArrayList<>();
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        if (!constraintViolations.isEmpty()) {
            log.error("");
            log.error("########## 显示所有不合法属性值 开始 ##########");
            // Set 转成 List 并排序
            for (ConstraintViolation<?> constraintViolation : constraintViolations) {
                log.error("## 属性名: `{}`, 属性值: `{}`({}), 错误消息：`{}`", getPropertyPath(constraintViolation),
                        getInvalidValue(constraintViolation), getFieldType(constraintViolation), constraintViolation.getMessage());

                String errorMsg = String.format("[%s = `%s`(%s)] - [%s]", getPropertyPath(constraintViolation),
                        getInvalidValue(constraintViolation), getFieldType(constraintViolation), constraintViolation.getMessage());
                errors.add(errorMsg);
            }
            log.error("########## 显示所有不合法属性值 结束 ##########");
            log.error("");
        }
        return errors;
    }

    /**
     * 获取属性类型
     *
     * @param constraintViolation ConstraintViolation实例
     * @return 属性类型
     */
    private String getFieldType(ConstraintViolation<?> constraintViolation) throws NoSuchFieldException {
        String fieldType;
        Object invalidValue = getInvalidValue(constraintViolation);
        if (invalidValue == null) {
            String propertyPath = getPropertyPath(constraintViolation);
            String propertyName = propertyPath.substring(propertyPath.lastIndexOf(".") + 1);
            Field field = constraintViolation.getLeafBean().getClass().getDeclaredField(propertyName);
            fieldType = field.getType().getSimpleName();
        } else {
            fieldType = invalidValue.getClass().getSimpleName();
        }
        return fieldType;
    }

    /**
     * 获取传入的无效的值【比如 前端传入的值，但未通过校验】
     *
     * @param constraintViolation ConstraintViolation实例
     * @return 无效的值
     */
    private Object getInvalidValue(ConstraintViolation<?> constraintViolation) {
        return constraintViolation.getInvalidValue();
    }

    /**
     * 获取不合法的属性【其对应的属性值未通过校验】
     *
     * @param constraintViolation ConstraintViolation实例
     * @return 不合法的属性
     */
    private String getPropertyPath(ConstraintViolation<?> constraintViolation) {
        return constraintViolation.getPropertyPath().toString();
    }

    @ResponseBody
    @ExceptionHandler(value = MyBatisSystemException.class)
    public Result<String> handleMyBatisSystemException(MyBatisSystemException e) {
        log.error(e.getMessage(), e);
        return Result.fail(e.getCause().getCause().getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Result<EmptyData> handleException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.fail();
    }

    @ResponseBody
    @ExceptionHandler(value = Throwable.class)
    public Result<EmptyData> handleThrowable(Throwable e) {
        log.error(e.getMessage(), e);
        return Result.fail();
    }
}
